package junior.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import junior.util.exception.ExceptionUtils;
import junior.util.function.TriFunction;
import junior.util.reflect.ReflectUtils;
import junior.util.value.ConvertParam;
import junior.util.value.ValueUtils;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtNewMethod;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Nullable;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

/**
 * Bean属性映射
 *
 * @author wubo
 * @date 2020-08-18
 */
public class BeanMapper {
    private static Logger logger = LoggerFactory.getLogger(BeanMapper.class);
    
    private final static BeanMapper SIMPLE = new BeanMapper();
    final static BeanMapper COMPATIBLE = new BeanMapper().autoType();
    
    private Class<? extends Annotation> includeAnnotation;
    private Class<? extends Annotation> excludeAnnotation;
    private List<String> includeProperties;
    private List<String> excludeProperties;
    private boolean containsStaticFields;
    private boolean skipNullValue;
    private boolean autoType;
    private boolean deepClone;
    private boolean immutable;
    private ConvertParam param;
    
    public ConvertParam getParam() {
        return param;
    }
    public ValueSetter getValueSetter() {
        return valueSetter;
    }
    
    private Map<String, String> keyMapping;
    private Map<Object, TriFunction<Object, Object, Object, Object>> valueMapping;
    
    private ValueSetter valueSetter;
    private String fingerPrint;
    
    public static BeanMapper create() {
        return new BeanMapper();
    }
    
    public static <T> T copyProperties(Object source, Class<T> targetType) {
        return SIMPLE.copy(source, targetType);
    }
    
    public static void copyProperties(Object source, Object target) {
        SIMPLE.copy(source, target);
    }
    
    public static Map<String, Object> copyToMap(Object source) {
        return SIMPLE.copyBeanToMap(source);
    }
    
    public static void deepCopy(Object source, Object target) {
        BeanMapper.create().deepClone().copy(source, target);
    }
    
    public static void deepCopyList(List sourceList, List targetList) {
        BeanMapper beanMapper = new BeanMapper().deepClone();
        for (Object source : sourceList) {
            if (source == null) {
                targetList.add(null);
            } else {
                targetList.add(beanMapper.deepClone().copy(source, source.getClass()));
            }
        }
    }
    
    public BeanMapper() {
        this.param = new ConvertParam();
        this.param.beanMapper(this);
        this.valueSetter = new ValueSetter();
        this.fingerPrint = "a".hashCode() + "";
    }
    
    public BeanMapper includeAnnotation(Class<? extends Annotation> annotation) {
        checkImmutable();
        this.includeAnnotation = annotation;
        calcFingerPrint();
        return this;
    }
    
    public BeanMapper excludeAnnotation(Class<? extends Annotation> annotation) {
        checkImmutable();
        this.excludeAnnotation = annotation;
        calcFingerPrint();
        return this;
    }
    
    public BeanMapper includeProperties(String... properties) {
        checkImmutable();
        if (properties != null) {
            if (properties.length > 0) {
                this.includeProperties = Arrays.asList(properties);
                calcFingerPrint();
            }
        }
        return this;
    }
    
    public BeanMapper excludeProperties(String... properties) {
        checkImmutable();
        if (properties != null) {
            if (properties.length > 0) {
                this.excludeProperties = Arrays.asList(properties);
                calcFingerPrint();
            }
        }
        return this;
    }
    
    public BeanMapper containsStaticFields() {
        checkImmutable();
        this.containsStaticFields = true;
        calcFingerPrint();
        return this;
    }
    
    public BeanMapper skipNullValue() {
        checkImmutable();
        this.skipNullValue = true;
        return this;
    }
    
    public BeanMapper disableSkipNullValue() {
        checkImmutable();
        this.skipNullValue = false;
        return this;
    }
    
    public BeanMapper deepClone() {
        checkImmutable();
        this.deepClone = true;
        this.param.deepCopy(true);
        calcFingerPrint();
        return this;
    }
    
    /**
     * 注意：此方法不一定生效
     *
     * @return
     */
    public BeanMapper disableDeepClone() {
        checkImmutable();
        this.deepClone = false;
        this.param.deepCopy(false);
        calcFingerPrint();
        return this;
    }
    
    public BeanMapper autoType() {
        checkImmutable();
        this.autoType = true;
        calcFingerPrint();
        return this;
    }
    
    public BeanMapper disableAutoType() {
        checkImmutable();
        this.autoType = false;
        calcFingerPrint();
        return this;
    }
    
    public BeanMapper immutable() {
        this.immutable = true;
        return this;
    }
    
    public BeanMapper keyMapping(String sourceProperty, String targetProperty) {
        checkImmutable();
        if (keyMapping == null) {
            keyMapping = new HashMap<>();
        }
        keyMapping.put(sourceProperty, targetProperty);
        calcFingerPrint();
        return this;
    }
    
    public <K> BeanMapper valueMapping(K sourceProperty, TriFunction<Object, Object, Object, Object> func) {
        checkImmutable();
        if (valueMapping == null) {
            valueMapping = new HashMap<>();
        }
        valueMapping.put(sourceProperty, func);
        calcFingerPrint();
        return this;
    }
    
    /**
     * 从Bean:source复制到自动实例化的Bean
     *
     * @param source
     * @param targetClass
     */
    public <T> T copy(Object source, Class<T> targetClass) {
        try {
            if (source == null) {
                return null;
            }
            if (targetClass.equals(source.getClass()) && !deepClone && BeanUtils.isNotDefinedBean(targetClass)) {
                return (T) source;
            }
            T target = targetClass.newInstance();
            copy(source, target);
            return target;
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    private BeanCopier copier;
    private Class<?> lastSourceType;
    private Class<?> lastTargetType;
    
    /**
     * 从Bean:source复制到Bean:target
     *
     * @param source
     * @param target
     */
    public void copy(Object source, Object target) {
        Assert.notNull(source, "Parameter source is required");
        Assert.notNull(target, "Parameter target is required");
        try {
            Class<?> sourceClass = source.getClass();
            Class<?> targetClass = target.getClass();
            if (this.isCopyAllProperties()) {
                boolean needConvertType = autoType && !sourceClass.equals(targetClass);
                boolean needDeepClone = deepClone;
                if (needConvertType && !deepClone) {
                    Boolean allSimpleType = sameTypeCache.get(getClassName(sourceClass) + "|" + getClassName(targetClass));
                    if (allSimpleType != null) {
                        if (needConvertType) {
                            needConvertType = !allSimpleType.booleanValue();
                        }
                        if (needDeepClone) {
                            needDeepClone = allSimpleType.booleanValue();
                        }
                    }
                }
                if (!needConvertType && !needDeepClone) {
                    boolean useConverter = skipNullValue || valueMapping != null;
                    BeanCopier copier = getBeanCopier(source, targetClass, useConverter);
                    copier.copy(source, target, useConverter ? (value, target1, setter) -> {
                        if (setter != null && setter.toString().startsWith("set")) {
                            String field = StringUtils.uncapitalize(setter.toString().substring(3));
                            if (skipNullValue && value == null) {
                                return null;
                            }
                            return valueSetter.getTargetValue(source, target, field, value, target1);
                        }
                        return value;
                    } : null);
                } else {
                    copyByJavaAssist(source, target, sourceClass, targetClass);
                }
            } else {
                copyByJavaAssist(source, target, sourceClass, targetClass);
            }
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    public interface CopyAssist {
        void _copy(Object source, Object target, BeanMapper beanMapper, ValueSetter valueSetter, ConvertParam param, boolean skipNullValue);
    }
    
    private static Map<String, CopyAssist> assistCache = new ConcurrentHashMap<>();
    private static Map<String, Boolean> sameTypeCache = new ConcurrentHashMap<>();
    private static Map<Class, String> typeNameCache = new ConcurrentHashMap<>();
    
    private void copyByJavaAssist(Object source, Object target, Class<?> sourceClass, Class<?> targetClass) {
        String srcTypeName = getClassName(sourceClass);
        String tgtTypeName = getClassName(targetClass);
        String key = srcTypeName + "2" + tgtTypeName + "$copy" + fingerPrint;
        CopyAssist assist = assistCache.get(key);
        if (assist == null) {
            assist = assistCache.computeIfAbsent(key, k -> {
                try {
                    ClassPool pool = ClassPool.getDefault();
                    CtClass ct = pool.makeClass(key, null);
                    ct.setInterfaces(new CtClass[]{pool.getCtClass(CopyAssist.class.getName())});
                    PropertyDescriptor[] sourcePds = getPropertyDescriptors(source.getClass());
                    
                    StringBuilder copyMethodContent = new StringBuilder();
                    copyMethodContent.append("public void _copy(Object source, Object target, junior.util.BeanMapper beanMapper, junior.util.BeanMapper.ValueSetter valueSetter, junior.util.value.ConvertParam param, boolean skipNullValue) {\n");
                    copyMethodContent.append(getTypeName(source.getClass()) + " src = (" + getTypeName(source.getClass()) + ") source;\n");
                    copyMethodContent.append(getTypeName(target.getClass()) + " tgt = (" + getTypeName(target.getClass()) + ") target;\n");
                    boolean allTypeSame = true;
                    boolean allTypeSimple = true;
                    for (PropertyDescriptor sourcePd : sourcePds) {
                        Method getter = sourcePd.getReadMethod();
                        if (getter == null) {
                            continue;
                        }
                        PropertyDescriptor targetPd = ReflectUtils.getPropertyDescriptor(targetClass, getTargetProperty(sourcePd.getName()));
                        if (targetPd == null || targetPd.getWriteMethod() == null) {
                            continue;
                        }
                        
                        Pair<Boolean, Boolean> pair = getPropertyCopyCode(copyMethodContent, sourcePd.getName(), targetPd.getName(), getter, targetPd.getReadMethod(), targetPd.getWriteMethod());
                        if (pair != null) {
                            if (pair.getLeft().booleanValue()) {
                                allTypeSame = false;
                            }
                            if (pair.getRight().booleanValue()) {
                                allTypeSimple = false;
                            }
                        }
                    }
                    copyMethodContent.append("}\n");
                    if (logger.isDebugEnabled()) {
                        logger.debug("===> Generate Code:\n{}", copyMethodContent);
                    }
                    ct.addMethod(CtNewMethod.make(copyMethodContent.toString(), ct));
                    Class clazz = ct.toClass(this.getClass().getClassLoader(), this.getClass().getProtectionDomain());
                    if (allTypeSame) {
                        sameTypeCache.putIfAbsent(srcTypeName + "|" + tgtTypeName, allTypeSimple ? Boolean.TRUE : Boolean.FALSE);
                    }
                    return (CopyAssist) clazz.newInstance();
                } catch (Exception e) {
                    throw ExceptionUtils.wrap2Runtime(e);
                }
            });
        }
        assist._copy(source, target, this, valueSetter, param, skipNullValue);
    }
    
    private Pair<Boolean, Boolean> getPropertyCopyCode(StringBuilder copyMethodContent, String sourceName, String targetName, Method getter, Method targetGetter, Method targetSetter) {
        Class<?> sourceType = getter.getReturnType();
        Class<?> targetType = targetSetter.getParameterTypes()[0];
        
        Type sourceGenericParameterType0 = getParameterizedType(getter.getGenericReturnType(), 0);
        Type targetGenericParameterType0 = getParameterizedType(targetSetter.getGenericParameterTypes(), 0, 0);
        
        boolean isArray = targetType.isArray();
        boolean isSimpleTargetType = org.springframework.beans.BeanUtils.isSimpleValueType(isArray ? targetType.getComponentType() : targetType);
        boolean isCollection = Collection.class.isAssignableFrom(targetType);
        boolean isSourceMap = Map.class.isAssignableFrom(sourceType);
        boolean isTargetMap = Map.class.isAssignableFrom(targetType);
        boolean typeDiff = !sourceType.equals(targetType);
        if (!typeDiff && isCollection) {
            typeDiff = !Objects.equals(sourceGenericParameterType0, targetGenericParameterType0);
        } else if (!typeDiff && isSourceMap && isTargetMap) {
            Type sourceGenericParameterType1 = getParameterizedType(getter.getGenericReturnType(), 1);
            Type targetGenericParameterType1 = getParameterizedType(targetSetter.getGenericParameterTypes(), 0, 1);
            typeDiff = !Objects.equals(sourceGenericParameterType0, targetGenericParameterType0)
                    || !Objects.equals(sourceGenericParameterType1, targetGenericParameterType1);
        }
        if (typeDiff && !autoType) {
            return null;
        }
        
        copyMethodContent.append(getCopyCode(sourceName, targetName, sourceType, targetType, typeDiff, getter, targetGetter, targetSetter,
                sourceGenericParameterType0, targetGenericParameterType0));
        
        return Pair.of(typeDiff, !isSimpleTargetType);
    }
    
    private String getCopyCode(String sourceName, String targetName, Class<?> sourceType, Class<?> targetType, boolean typeDiff,
                               Method getter, Method targetGetter, Method targetSetter,
                               Type sourceGenericParameterType0, Type targetGenericParameterType0) {
        StringBuilder codeBuff = new StringBuilder();
        
        String sourceTypeName = getTypeName(sourceType);
        String targetTypeName = getTypeName(targetType);
        boolean isTargetTypePrimitive = targetType.isPrimitive();
        Class<?> subType = targetType.isArray() ? targetType.getComponentType() : targetType;
        boolean isFinalTargetType = Modifier.isFinal(subType.getModifiers());
        Class<?> objectTargetType = isTargetTypePrimitive ? ClassUtils.resolvePrimitiveIfNecessary(targetType) : targetType;
        
        boolean hasValueMapping = valueMapping != null && valueMapping.containsKey(sourceName);
        
        String valueCall = targetName + "SrcVal";
        boolean isSourceTypePrimitive = sourceType.isPrimitive();
        if (isSourceTypePrimitive) {
            Class<?> boxedType = ClassUtils.resolvePrimitiveIfNecessary(sourceType);
            valueCall = boxedType.getName() + ".valueOf(" + targetName + "SrcVal)";
        }
        String varType = targetTypeName;
        
        codeBuff.append(String.format("%s %sSrcVal = src.%s();\n", sourceTypeName, targetName, getter.getName()));
        if (!isSourceTypePrimitive) {
            codeBuff.append(String.format("if (%sSrcVal == null", targetName)).append(isTargetTypePrimitive ? "" : " && !skipNullValue").append(") {\n");
            if (hasValueMapping) {
                codeBuff.append(String.format("%s %s = (%s) valueSetter.applyValueMapping(src, tgt, \"%s\", " + valueCall + ");\n",
                        objectTargetType.getName(), targetName, objectTargetType.getName(), sourceName, targetName));
                codeBuff.append(String.format("tgt.%s(%s);\n", targetSetter.getName(), targetName));
            } else if (!isTargetTypePrimitive) {
                codeBuff.append(String.format("tgt.%s(null);\n", targetSetter.getName()));
            }
            codeBuff.append(String.format("} else if (%sSrcVal != null) {\n", targetName));
        }
        
        if (hasValueMapping) {
            codeBuff.append(String.format("%s %s = (%s) valueSetter.applyValueMapping(src, tgt, \"%s\", " + valueCall + ");\n",
                    objectTargetType.getName(), targetName, objectTargetType.getName(), sourceName, targetName));
            varType = objectTargetType.getName();
        } else {
            // 执行复制
            if (typeDiff || deepClone) {
                codeBuff.append(getObjectCopyCode(sourceName, targetName, targetTypeName, valueCall,
                        sourceType, targetType, objectTargetType, targetGetter,
                        sourceGenericParameterType0, targetGenericParameterType0,
                        typeDiff));
                
                if (typeDiff || (deepClone && !isFinalTargetType)) {
                    varType = getTypeName(objectTargetType);
                }
            } else {
                codeBuff.append(String.format("%s %s = %sSrcVal;\n", targetTypeName, targetName, targetName));
            }
        }
        
        // 如果为简单类型且当前变量是简单类型的封装类型，做下转换
        if (isTargetTypePrimitive && !varType.equals(targetTypeName)) {
            codeBuff.append(String.format("if (%s != null) {\n", targetName));
            codeBuff.append(String.format("tgt.%s(%s.%sValue());\n", targetSetter.getName(), targetName, targetTypeName));
            codeBuff.append("}\n");
        } else {
            codeBuff.append(String.format("tgt.%s(%s);\n", targetSetter.getName(), targetName));
        }
        
        if (!isSourceTypePrimitive) {
            codeBuff.append("}\n");
        }
        
        return codeBuff.toString();
    }
    
    private String getObjectCopyCode(String sourceName, String targetName, String targetTypeName, String valueCall,
                                     Class<?> sourceType, Class<?> targetType, Class<?> objectTargetType, @Nullable Method targetGetter,
                                     Type sourceGenericParameterType0, Type targetGenericParameterType0,
                                     boolean typeDiff) {
        StringBuilder codeBuff = new StringBuilder();
        
        boolean isArray = targetType.isArray();
        boolean isCollection = Collection.class.isAssignableFrom(targetType);
        boolean isSourceMap = Map.class.isAssignableFrom(sourceType);
        boolean isTargetMap = Map.class.isAssignableFrom(targetType);
        Class<?> subType = targetType.isArray() ? targetType.getComponentType() : targetType;
        boolean isSimpleTargetType = org.springframework.beans.BeanUtils.isSimpleValueType(subType);
        boolean isFinalTargetType = Modifier.isFinal(subType.getModifiers());
        
        // 声明变量
        if (!isSimpleTargetType && (typeDiff || deepClone)) {
            if (isCollection) {
                String newTargetInstance = "new java.util.ArrayList()";
                if (Set.class.isAssignableFrom(targetType)) {
                    newTargetInstance = "new java.util.HashSet()";
                }
                if (targetGetter != null) {
                    codeBuff.append(String.format("%s %s = tgt.%s();\n", targetTypeName, targetName, targetGetter.getName()));
                    codeBuff.append(String.format("if (%s == null) {\n", targetName));
                    codeBuff.append(String.format("%s = %s;\n", targetName, newTargetInstance));
                    codeBuff.append("}\n");
                } else {
                    codeBuff.append(String.format("%s %s = %s;\n", targetTypeName, targetName, newTargetInstance));
                }
            } else if (isSourceMap || isTargetMap) {
                String newTargetInstance = "new java.util.HashMap()";
                if (isSourceMap && !isTargetMap) {
                    newTargetInstance = "new " + targetTypeName + "()";
                }
                if (targetGetter != null) {
                    codeBuff.append(String.format("%s %s = tgt.%s();\n", targetTypeName, targetName, targetGetter.getName()));
                    codeBuff.append(String.format("if (%s == null) {\n", targetName));
                    codeBuff.append(String.format("%s = %s;\n", targetName, newTargetInstance));
                    codeBuff.append("}\n");
                } else {
                    codeBuff.append(String.format("%s %s = %s;\n", targetTypeName, targetName, newTargetInstance));
                }
            } else if (isArray) {
                codeBuff.append(String.format("%s %s = %s;\n", targetTypeName, targetName, getTypeNameNew(targetType, targetName + "SrcVal")));
            } else {
                if (targetGetter != null) {
                    codeBuff.append(String.format("%s %s = tgt.%s();\n", targetTypeName, targetName, targetGetter.getName()));
                    codeBuff.append(String.format("if (%s == null) {\n", targetName));
                    codeBuff.append(String.format("%s = %s;\n", targetName, getTypeNameNew(targetType, targetName + "SrcVal")));
                    codeBuff.append("}\n");
                } else {
                    codeBuff.append(String.format("%s %s = %s;\n", targetTypeName, targetName, getTypeNameNew(targetType, targetName + "SrcVal")));
                }
            }
        }
        if (isSimpleTargetType) {
            if (typeDiff || (deepClone && !isFinalTargetType)) {
                codeBuff.append(String.format("%s %s = (%s) valueSetter.getTargetValue(src, tgt, \"%s\", " + valueCall + ", %s.class);\n",
                        getTypeName(objectTargetType), targetName, getTypeName(objectTargetType), sourceName, getTypeName(targetType)));
            } else {
                codeBuff.append(String.format("%s %s = %sSrcVal;\n", targetTypeName, targetName, targetName));
            }
        } else if (isCollection) {
            codeBuff.append(getCollectionCopyCode( targetName, targetType, sourceGenericParameterType0, targetGenericParameterType0, typeDiff));
        } else if (isSourceMap || isTargetMap) {
            codeBuff.append(getMapCopyCode(targetName, targetGenericParameterType0, targetGetter != null ? getParameterizedType(targetGetter.getGenericReturnType(), 1) : getParameterizedType(subType, 1), isSourceMap, isTargetMap));
        } else if (isArray) {
            Class<?> sourceSubType = sourceType.isArray() ? sourceType.getComponentType() : sourceType;
            boolean subTypeDiff = !Objects.equals(sourceSubType, subType);
            String sourceSubTypeName = getTypeName(sourceSubType);
            String subTypeName = getTypeName(subType);
            
            codeBuff.append(String.format("for (int %sIndex=0; %sIndex<%sSrcVal.length; %sIndex++) {\n", targetName, targetName, targetName, targetName));
            codeBuff.append(String.format("%s %sItemSrcVal = %sSrcVal[%sIndex];\n", sourceSubTypeName, targetName, targetName, targetName));
            codeBuff.append(getObjectCopyCode(targetName + "ItemSrcVal", targetName + "Item", subTypeName, targetName + "ItemSrcVal", sourceSubType, subType, subType, null,
                    getParameterizedType(sourceSubType, 0), getParameterizedType(subType, 0), subTypeDiff));
            codeBuff.append(String.format("%s[%sIndex] = %sItem;\n", targetName, targetName, targetName));
            codeBuff.append("}\n");
            codeBuff.append(String.format("System.arraycopy(%sSrcVal, 0, %s, 0, %sSrcVal.length);\n", targetName, targetName, targetName));
        } else {
            codeBuff.append(String.format("beanMapper.copy(%sSrcVal, %s);\n", targetName, targetName));
        }
        
        return codeBuff.toString();
    }
    
    private String getMapCopyCode(String targetName, Type targetGenericParameterType0, Type targetGenericParameterType1, boolean isSourceMap, boolean isTargetMap) {
        StringBuilder codeBuff = new StringBuilder();
        
        boolean fillTypeParams = true;
        String method = "copyMapToMap";
        if (isSourceMap && !isTargetMap) {
            method = "copyMapToBean";
            fillTypeParams = false;
        } else if (!isSourceMap && isTargetMap) {
            method = "copyBeanToMap";
            fillTypeParams = false;
        }
        
        codeBuff.append("beanMapper.").append(method);
        codeBuff.append(String.format("(%sSrcVal, %s", targetName, targetName));
        codeBuff.append(fillTypeParams && targetGenericParameterType0 != null ? String.format(", %s.class, %s.class", getTypeName(targetGenericParameterType0), ValueUtils.ifNull(targetGenericParameterType1, Object.class).getTypeName()) : "").append(");\n");
        
        return codeBuff.toString();
    }
    
    private String getCollectionCopyCode(String targetName, Class<?> targetType, Type sourceGenericParameterType0, Type targetGenericParameterType0, boolean typeDiff) {
        StringBuilder codeBuff = new StringBuilder();
        
        codeBuff.append(String.format("for (java.util.Iterator %sSrcIte = %sSrcVal.iterator();  %sSrcIte.hasNext();) {\n", targetName, targetName, targetName));
        
        if (sourceGenericParameterType0 != null && targetGenericParameterType0 != null) {
            boolean subTypeDiff = !Objects.equals(sourceGenericParameterType0, targetGenericParameterType0);
            String sourceSubTypeName = getTypeName(sourceGenericParameterType0);
            String subTypeName = getTypeName(targetGenericParameterType0);
            
            codeBuff.append(String.format("%s %sItemSrcVal = (%s) %sSrcIte.next();\n", sourceSubTypeName, targetName, sourceSubTypeName, targetName));
            codeBuff.append(getObjectCopyCode(targetName + "Item", targetName + "Item", subTypeName, targetName + "ItemSrcVal",
                    (Class<?>) sourceGenericParameterType0, (Class<?>) targetGenericParameterType0, (Class<?>) targetGenericParameterType0, null,
                    getParameterizedType(sourceGenericParameterType0, 0), getParameterizedType(targetGenericParameterType0, 0), subTypeDiff));
            codeBuff.append(String.format("%s.add(%sItem);\n", targetName, targetName));
        } else {
            if (targetGenericParameterType0 == null) {
                codeBuff.append(String.format("Object %sItemSrcVal = %sSrcIte.next();\n", targetName, targetName));
                codeBuff.append(String.format("%s.add(", targetName))
                        .append(String.format("valueSetter.doConvertValue(%sItemSrcVal, %sItemSrcVal.getClass())", targetName, targetName))
                        .append(");\n");
            } else {
                String subTypeName = getTypeName(targetGenericParameterType0);
                codeBuff.append(String.format("%s.add(", targetName))
                        .append(String.format("valueSetter.doConvertValue(%sSrcIte.next(), %s.class)", targetName, subTypeName))
                        .append(");\n");
            }
        }
        
        codeBuff.append("}\n");
        
        return codeBuff.toString();
    }
    
    private String getTypeName(Type type) {
        if (type instanceof Class && ((Class) type).isArray()) {
            Class clazz = (Class) type;
            return clazz.getComponentType().getName() + "[]";
        }
        return type.getTypeName();
    }
    
    private String getTypeNameNew(Type type, String var) {
        if (type instanceof Class && ((Class) type).isArray()) {
            Class clazz = (Class) type;
            return "new " + clazz.getComponentType().getName() + "[" + (var != null ? var + ".length" : "1") + "]";
        }
        return "new " + type.getTypeName() + "()";
    }
    
    private void copyByMethodReflected(Object source, Object target, Class<?> targetClass, PropertyDescriptor[] pds) throws IllegalAccessException, InvocationTargetException {
        for (PropertyDescriptor pd : pds) {
            Method readMethod = pd.getReadMethod();
            Object value = readMethod.invoke(source);
            if (value == null && skipNullValue) {
                continue;
            }
            
            PropertyDescriptor tgtPd = ReflectUtils.getPropertyDescriptor(targetClass, this.getTargetProperty(pd.getName()));
            if (tgtPd == null) {
                continue;
            }
            Method writeMethod = tgtPd.getWriteMethod();
            
            if (writeMethod != null && Modifier.isPublic(writeMethod.getModifiers()) && !Modifier.isStatic(writeMethod.getModifiers())) {
                writeMethod.invoke(target, !autoType ? valueSetter.getTargetValue(source, target, pd.getName(), value, writeMethod.getParameterTypes()[0])
                        : valueSetter.getTargetValueAuto(source, target, pd.getName(), value, writeMethod.getGenericParameterTypes()[0]));
            }
        }
    }
    
    private String getClassName(Class clazz) {
        return typeNameCache.computeIfAbsent(clazz, (k) -> clazz.getName());
    }
    
    private BeanCopier getBeanCopier(Object source, Class<?> targetClass, boolean useConverter) {
        if (copier == null || (source.getClass() != lastSourceType || targetClass != lastTargetType)) {
            copier = BeanCopier.create(source.getClass(), targetClass, useConverter);
            lastSourceType = source.getClass();
            lastTargetType = targetClass;
        }
        return copier;
    }
    
    /**
     * 复制: source和target可以是Bean或者Map，自动识别
     *
     * @param source
     * @param target
     */
    public void copyAuto(Object source, Object target) {
        Assert.notNull(source, "Parameter source is required");
        Assert.notNull(target, "Parameter target is required");
        
        try {
            Class srcClass = source.getClass();
            Class targetClass = target.getClass();
            
            boolean isSrcMap = Map.class.isAssignableFrom(srcClass);
            boolean isTargetMap = Map.class.isAssignableFrom(targetClass);
            
            if (!isSrcMap) {
                if (isTargetMap) {
                    copyBeanToMap(source, (Map<String, Object>) target);
                } else {
                    copy(source, target);
                }
            } else {
                if (isTargetMap) {
                    copyMapToMap((Map<String, Object>) source, (Map<String, Object>) target);
                } else {
                    copyMapToBean((Map<String, Object>) source, target);
                }
            }
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    /**
     * 复制: source和target可以是Bean或者Map，自动识别
     *
     * @param source
     * @param targetClass
     */
    public <T> T copyAuto(Object source, Class<T> targetClass) {
        Assert.notNull(source, "Parameter source is required");
        Assert.notNull(targetClass, "Parameter targetClass is required");
        
        try {
            Class srcClass = source.getClass();
            
            boolean isSrcMap = Map.class.isAssignableFrom(srcClass);
            boolean isTargetMap = Map.class.isAssignableFrom(targetClass);
            
            if (!isSrcMap) {
                if (isTargetMap) {
                    return ValueUtils.convertValue(source, targetClass, this.getParam());
                } else {
                    return copy(source, targetClass);
                }
            } else {
                if (isTargetMap) {
                    return ValueUtils.convertValue(source, targetClass, this.getParam());
                } else {
                    return copyMapToBean((Map<String, Object>) source, targetClass);
                }
            }
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    /**
     * 列表复制：从Bean:source列表复制到自动实例化的Bean列表
     *
     * @param sources
     * @param targetElementClass
     */
    public <T> List<T> copyList(List<? extends Object> sources, Class<T> targetElementClass) {
        Assert.notNull(sources, "Parameter sources is required");
        Assert.notNull(targetElementClass, "Parameter targetElementClass is required");
        
        List<T> result = new ArrayList<T>();
        for (Object obj : sources) {
            result.add(copy(obj, targetElementClass));
        }
        return result;
    }
    
    /**
     * 列表复制：从Bean:source列表复制到自动实例化的Bean列表
     *
     * @param sources
     * @param targetElementClass
     */
    public <T> List<T> copyListAuto(List<? extends Object> sources, Class<T> targetElementClass) {
        Assert.notNull(sources, "Parameter sources is required");
        Assert.notNull(targetElementClass, "Parameter targetElementClass is required");
        try {
            List<T> result = new ArrayList<T>();
            for (Object obj : sources) {
                T target = targetElementClass.newInstance();
                copyAuto(obj, target);
                result.add(target);
            }
            return result;
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    /**
     * 列表复制：从Bean:source列表复制到Bean:target列表
     *
     * @param sources
     * @param targets
     * @param targetElementClass
     */
    public <T> void copyList(List<? extends Object> sources, List<T> targets, Class<T> targetElementClass) {
        Assert.notNull(sources, "Parameter sources is required");
        Assert.notNull(targets, "Parameter targets is required");
        Assert.notNull(targetElementClass, "Parameter targetElementClass is required");
        int index = 0;
        for (Object obj : sources) {
            if (targets.size() > index) {
                copy(obj, targets.get(index ++));
            } else {
                targets.add(copy(obj, targetElementClass));
            }
        }
    }
    
    /**
     * 列表复制：从Bean:source列表复制到Bean:target列表
     *
     * @param sources
     * @param targets
     * @param targetElementClass
     */
    public <T> void copyListAuto(List<? extends Object> sources, List<T> targets, Class<T> targetElementClass) {
        Assert.notNull(sources, "Parameter sources is required");
        Assert.notNull(targets, "Parameter targets is required");
        Assert.notNull(targetElementClass, "Parameter targetElementClass is required");
        int index = 0;
        for (Object obj : sources) {
            if (targets.size() > index) {
                copy(obj, targets.get(index++));
            } else {
                targets.add(copy(obj, targetElementClass));
            }
        }
    }
    
    /**
     * 复制列表: List Map复制到List Bean
     *
     * @param sources
     * @param beanClass
     */
    public <T> List<T> copyMapsToBeans(List<Map<String, Object>> sources, Class<T> beanClass) {
        if (sources == null) return null;
        if (sources.size() == 0) return Collections.EMPTY_LIST;
        
        List<T> target = Lists.newArrayListWithExpectedSize(sources.size());
        for (Map<String, Object> source : sources) {
            target.add(copyMapToBean(source, beanClass));
        }
        return target;
    }
    
    /**
     * 复制列表: List Bean复制到List Map
     *
     * @param sources
     */
    public List<Map<String, Object>> copyBeansToMaps(List<? extends Object> sources) {
        if (sources == null) return null;
        if (sources.size() == 0) return Collections.EMPTY_LIST;
        
        List<Map<String, Object>> maps = Lists.newArrayListWithExpectedSize(sources.size());
        
        PropertyDescriptor[] pds = this.getPropertyDescriptors(sources.get(0).getClass());
        try {
            for (Object bean : sources) {
                Map<String, Object> map = Maps.newHashMapWithExpectedSize(pds.length);
                
                for (PropertyDescriptor pd : this.getPropertyDescriptors(bean.getClass())) {
                    Object value = pd.getReadMethod().invoke(bean);
                    if (value == null && skipNullValue) {
                        continue;
                    }
                    map.put(this.getTargetProperty(pd.getName()), valueSetter.getTargetValue(bean, map, pd.getName(), value, null));
                }
                maps.add(map);
            }
            return maps;
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    /**
     * 从Map:source复制到Map:target
     *
     * @param source
     * @param target
     */
    public <K> void copyMapToMap(Map<K, Object> source, Map<K, Object> target) {
        copyMapToMap(source, target, null);
    }
    
    /**
     * 从Map:source复制到Map:target，可指定Map的值类型
     *
     * @param source
     * @param target
     * @param targetValueType
     * @param <K>
     */
    public <K> void copyMapToMap(Map<K, Object> source, Map<K, Object> target, Class<?> targetValueType) {
        this.onEntrySet(source, (k, v) -> {
            if (v != null || !skipNullValue) {
                target.put(k, valueSetter.getTargetValue(source, target, k, v, targetValueType));
            }
        });
    }
    
    /**
     * 从Map:source复制到Map:target，可指定Map的键、值类型
     *
     * @param source
     * @param target
     * @param targetKeyType
     * @param targetValueType
     */
    public void copyMapToMap(Map<Object, Object> source, Map<Object, Object> target, Class<?> targetKeyType, Class<?> targetValueType) {
        this.onEntrySet(source, (k, v) -> {
            if (v != null || !skipNullValue) {
                target.put(valueSetter.doConvertValue(k, targetKeyType), valueSetter.getTargetValue(source, target, k, v, targetValueType));
            }
        });
    }
    
    /**
     * 从Bean:source复制到自动创建的HashMap:target
     *
     * @param source
     */
    public Map<String, Object> copyBeanToMap(Object source) {
        if (source == null) {
            return null;
        }
        Map<String, Object> map = Maps.newHashMap();
        copyBeanToMap(source, map);
        return map;
    }
    
    /**
     * 从Bean:source复制到Map:target
     *
     * @param source
     * @param target
     */
    public void copyBeanToMap(Object source, Map<String, Object> target) {
        if (source == null || target == null) return;
        BeanMap map = BeanMap.create(source);
        copyMapToMap(map, target);
    }
    
    /**
     * 从Map:source复制到自动实例化的Bean
     *
     * @param source
     * @param beanClass
     */
    public <T> T copyMapToBean(Map<String, Object> source, Class<T> beanClass) {
        try {
            T target = beanClass.newInstance();
            
            copyMapToBean(source, target);
            
            return target;
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    /**
     * 从Map:source复制到Bean:target
     *
     * @param source
     * @param target
     */
    public void copyMapToBean(Map<String, Object> source, Object target) {
        this.onEntrySet(source, (k, v) -> {
            try {
                PropertyDescriptor pd = ReflectUtils.getPropertyDescriptor(target.getClass(), k);
                if (pd != null) {
                    if (v != null || !skipNullValue) {
                        Method setter = pd.getWriteMethod();
                        if (setter != null
                                && Modifier.isPublic(setter.getModifiers())
                                && (!Modifier.isStatic(setter.getModifiers()) || this.isContainsStaticFields())) {
                            setter.invoke(target, !autoType ? valueSetter.getTargetValue(source, target, pd.getName(), v, setter.getParameterTypes()[0])
                                    : valueSetter.getTargetValueAuto(source, target, pd.getName(), v, setter.getGenericParameterTypes()[0]));
                        }
                    }
                }
            } catch (Exception e) {
                throw ExceptionUtils.wrap2Runtime(e);
            }
        });
    }
    
    private PropertyDescriptor[] getExcludePropertyDescriptors(Class<?> beanClass) {
        if (excludeAnnotation == null && excludeProperties == null && keyMapping == null) {
            return null;
        }
        List<PropertyDescriptor> result = Lists.newArrayList();
        
        PropertyDescriptor[] pds = ReflectUtils.getPropertyDescriptors(beanClass);
        for (PropertyDescriptor pd : pds) {
            if (excludeProperties != null && excludeProperties.contains(pd.getName())) {
                result.add(pd);
                continue;
            }
            if (keyMapping != null && keyMapping.containsKey(pd.getName())) {
                result.add(pd);
                continue;
            }
            Method getter = pd.getReadMethod();
            if (getter != null
                    && Modifier.isPublic(getter.getModifiers())
                    && (!Modifier.isStatic(getter.getModifiers()) || containsStaticFields)) {
                if (excludeAnnotation != null) {
                    if (getter.isAnnotationPresent(excludeAnnotation)) {
                        result.add(pd);
                        continue;
                    }
                }
            }
        }
        if (result.isEmpty()) {
            return null;
        }
        return result.toArray(new PropertyDescriptor[result.size()]);
    }
    
    private PropertyDescriptor[] getPropertyDescriptors(Class<?> beanClass) {
        List<PropertyDescriptor> result = Lists.newArrayList();
        
        PropertyDescriptor[] pds = ReflectUtils.getPropertyDescriptors(beanClass);
        boolean includeMode = includeProperties != null || includeAnnotation != null;
        
        for (PropertyDescriptor pd : pds) {
            if (excludeProperties != null && excludeProperties.contains(pd.getName())) {
                continue;
            }
            Method getter = pd.getReadMethod();
            if (getter != null
                    && Modifier.isPublic(getter.getModifiers())
                    && (!Modifier.isStatic(getter.getModifiers()) || containsStaticFields)) {
                if (excludeAnnotation != null) {
                    if (getter.isAnnotationPresent(excludeAnnotation)) {
                        continue;
                    }
                }
                if (includeProperties != null && includeProperties.contains(pd.getName())) {
                    result.add(pd);
                }
                if (includeAnnotation != null) {
                    if (getter.isAnnotationPresent(includeAnnotation)) {
                        result.add(pd);
                    }
                }
                if (!includeMode) {
                    result.add(pd);
                }
            }
        }
        
        return result.toArray(new PropertyDescriptor[result.size()]);
    }
    
    private <K> void onEntrySet(Map<K, Object> data, BiConsumer<K, Object> consumer) {
        if (data != null) {
            for (Map.Entry<K, Object> entry : data.entrySet()) {
                if (excludeProperties != null && excludeProperties.contains(entry.getKey())) {
                    continue;
                }
                K key = entry.getKey();
                if (key != null && key instanceof String) {
                    key = (K) getTargetProperty((String) key);
                }
                if (includeProperties != null) {
                    if (includeProperties.contains(entry.getKey())) {
                        consumer.accept(key, entry.getValue());
                    }
                } else {
                    consumer.accept(key, entry.getValue());
                }
            }
        }
    }
    
    private String getTargetProperty(String sourceProperty) {
        if (keyMapping == null) {
            return sourceProperty;
        }
        String targetProperty = keyMapping.get(sourceProperty);
        if (targetProperty == null) {
            return sourceProperty;
        }
        return targetProperty;
    }
    
    private Type getParameterizedType(Type[] types, int index, int typeArgsIndex) {
        if (types.length >= index) {
            return getParameterizedType(types[index], typeArgsIndex);
        }
        return null;
    }
    private Type getParameterizedType(Type type, int index) {
        if (type == null) {
            return null;
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            if (parameterizedType.getActualTypeArguments() != null && parameterizedType.getActualTypeArguments().length > index) {
                return parameterizedType.getActualTypeArguments()[index];
            }
        }
        return null;
    }
    
    private boolean isContainsStaticFields() {
        return containsStaticFields;
    }
    
    private boolean isCopyAllProperties() {
        return includeAnnotation == null && includeProperties == null && excludeProperties == null && excludeAnnotation == null && keyMapping == null;
    }
    
    private void checkImmutable() {
        if (immutable) {
            throw new IllegalStateException("Instance is immutable");
        }
    }
    
    private void calcFingerPrint() {
        StringBuilder buff = new StringBuilder();
        if (autoType) {
            buff.append("a");
        }
        if (deepClone) {
            buff.append("d");
        }
        if (containsStaticFields) {
            buff.append("s");
        }
        if (includeProperties != null) {
            buff.append(includeProperties.toString().replaceAll("[,]", "1").replaceAll("[=]", "2").replaceAll("[\\[]", "3").replaceAll("[\\]]", "9").replaceAll("\\s", ""));
        }
        if (excludeProperties != null) {
            buff.append(excludeProperties.toString().replaceAll("[,]", "1").replaceAll("[=]", "2").replaceAll("[\\[]", "3").replaceAll("[\\]]", "9").replaceAll("\\s", ""));
        }
        if (includeAnnotation != null) {
            buff.append(includeAnnotation.getName());
        }
        if (excludeAnnotation != null) {
            buff.append(excludeAnnotation.getName());
        }
        if (keyMapping != null) {
            buff.append(keyMapping.toString().replaceAll("[,]", "1").replaceAll("[=]", "2").replaceAll("[\\[]", "3").replaceAll("[\\]]", "9").replaceAll("\\s", ""));
        }
        if (valueMapping != null) {
            buff.append(valueMapping.toString().replaceAll("[,]", "1").replaceAll("[=]", "2").replaceAll("[\\[]", "3").replaceAll("[\\]]", "9").replaceAll("\\s", ""));
        }
        String str = buff.toString();this.fingerPrint = str.length() > 20 ? str.substring(0, 15) + str.hashCode() : str;
    }
    
    public class ValueSetter {
        private ValueSetter() {}
        
        public <K> Object getTargetValueAuto(Object source, Object target, K sourceProperty, Object sourceValue, Type targetType) {
            if (targetType instanceof ParameterizedType) {
                return getTargetValueWithParameterizedType(source, target, sourceProperty, sourceValue, (ParameterizedType) targetType);
            }
            return getTargetValue(source, target, sourceProperty, sourceValue, (Class<?>) targetType);
        }
        
        public <K> Object getTargetValue(Object source, Object target, K sourceProperty, Object sourceValue, Class<?> targetType) {
            if (valueMapping == null) {
                return targetType != null ? doConvertValue(sourceValue, targetType) : sourceValue;
            }
            TriFunction<Object, Object, Object, Object> func = valueMapping.get(sourceProperty);
            if (func == null) {
                return targetType != null ? doConvertValue(sourceValue, targetType) : sourceValue;
            }
            return func.apply(source, target, sourceValue);
        }
        
        public <K> Object applyValueMapping(Object source, Object target, K sourceProperty, Object sourceValue) {
            if (valueMapping != null) {
                TriFunction<Object, Object, Object, Object> func = valueMapping.get(sourceProperty);
                return func.apply(source, target, sourceValue);
            }
            return sourceValue;
        }
        
        private <K> Object getTargetValueWithParameterizedType(Object source, Object target, K sourceProperty, Object sourceValue, ParameterizedType targetType) {
            if (valueMapping == null) {
                return targetType != null ? doConvertValueWithParameterizedType(sourceValue, targetType) : sourceValue;
            }
            TriFunction<Object, Object, Object, Object> func = valueMapping.get(sourceProperty);
            if (func == null) {
                return targetType != null ? doConvertValueWithParameterizedType(sourceValue, targetType) : sourceValue;
            }
            return func.apply(source, target, sourceValue);
        }
        
        public <K> Object doConvertValue(K sourceValue, Class<?> targetType) {
            return deepClone || autoType ? ValueUtils.convert(sourceValue, targetType, param) : sourceValue;
        }
        
        public <K> Object doConvertValueWithParameterizedType(K sourceValue, ParameterizedType targetType) {
            return deepClone || autoType ? ValueUtils.convert(sourceValue, targetType, param) : sourceValue;
        }
    }
    
}
